< prev index next >

src/java.base/share/classes/java/lang/reflect/Proxy.java

Print this page
@@ -29,10 +29,11 @@
  import java.lang.invoke.MethodHandles;
  import java.lang.invoke.MethodType;
  import java.lang.invoke.WrongMethodTypeException;
  import java.lang.module.ModuleDescriptor;
  import java.util.ArrayDeque;
+ import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.Deque;
  import java.util.HashMap;
  import java.util.HashSet;

@@ -48,10 +49,11 @@
  
  import jdk.internal.access.JavaLangAccess;
  import jdk.internal.access.SharedSecrets;
  import jdk.internal.module.Modules;
  import jdk.internal.misc.VM;
+ import jdk.internal.misc.CDS;
  import jdk.internal.loader.ClassLoaderValue;
  import jdk.internal.vm.annotation.Stable;
  import sun.reflect.misc.ReflectUtil;
  
  import static java.lang.invoke.MethodType.methodType;

@@ -399,31 +401,51 @@
                  (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
              );
          }
      }
  
+     /**
+      * Called from VM native code during dump time.
+      */
+     private static void initCacheForCDS(ClassLoader platformLoader, ClassLoader appLoader) {
+         ProxyBuilder.initCacheForCDS(platformLoader, appLoader);
+     }
+ 
      /**
       * Builder for a proxy class.
       *
       * If the module is not specified in this ProxyBuilder constructor,
       * it will map from the given loader and interfaces to the module
       * in which the proxy class will be defined.
       */
      private static final class ProxyBuilder {
          private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
  
+         private static String makeProxyClassNamePrefix() {
+             // Allow unique proxy names to be used across CDS dump time and app run time.
+             if (CDS.isDumpingArchive()) {
+                 if (CDS.isUsingArchive()) {
+                     return "$Proxy0100"; // CDS dynamic dump
+                 } else {
+                     return "$Proxy0010"; // CDS static dump
+                 }
+             } else {
+                 return "$Proxy";
+             }
+         }
+ 
          // prefix for all proxy class names
-         private static final String proxyClassNamePrefix = "$Proxy";
+         private static final String proxyClassNamePrefix = makeProxyClassNamePrefix();
  
          // next number to use for generation of unique proxy class names
          private static final AtomicLong nextUniqueNumber = new AtomicLong();
  
          // a reverse cache of defined proxy classes
          private static final ClassLoaderValue<Boolean> reverseProxyCache =
              new ClassLoaderValue<>();
  
-         private record ProxyClassContext(Module module, String packageName, int accessFlags) {
+         private record ProxyClassContext(Module module, String packageName, int accessFlags, boolean isDynamicModule) {
              private ProxyClassContext {
                  if (module.isNamed()) {
                      if (packageName.isEmpty()) {
                          // Per JLS 7.4.2, unnamed package can only exist in unnamed modules.
                          // This means a package-private superinterface exist in the unnamed

@@ -454,23 +476,37 @@
  
          private static Class<?> defineProxyClass(ProxyClassContext context, List<Class<?>> interfaces) {
              /*
               * Choose a name for the proxy class to generate.
               */
+             String packagePrefix = context.packageName().isEmpty()
+                                     ? proxyClassNamePrefix
+                                     : context.packageName() + "." + proxyClassNamePrefix;
+             ClassLoader loader = context.module().getClassLoader();
+             int accessFlags = context.accessFlags() | Modifier.FINAL;
+ 
+             if (archivedData != null) {
+                 Class<?> pc = archivedData.getArchivedProxyClass(loader, packagePrefix, interfaces);
+                 if (pc != null) {
+                     reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
+                     return pc;
+                 }
+             }
+ 
              long num = nextUniqueNumber.getAndIncrement();
-             String proxyName = context.packageName().isEmpty()
-                                     ? proxyClassNamePrefix + num
-                                     : context.packageName() + "." + proxyClassNamePrefix + num;
+             String proxyName = packagePrefix + num;
  
-             ClassLoader loader = context.module().getClassLoader();
              trace(proxyName, context.module(), loader, interfaces);
  
+             if (CDS.isLoggingDynamicProxies() && context.isDynamicModule()) {
+                 CDS.logDynamicProxy(loader, proxyName, interfaces.toArray(new Class<?>[0]), accessFlags);
+             }
+ 
              /*
               * Generate the specified proxy class.
               */
-             byte[] proxyClassFile = ProxyGenerator.generateProxyClass(loader, proxyName, interfaces,
-                                                                       context.accessFlags() | Modifier.FINAL);
+             byte[] proxyClassFile = ProxyGenerator.generateProxyClass(loader, proxyName, interfaces, accessFlags);
              try {
                  Class<?> pc = JLA.defineClass(loader, proxyName, proxyClassFile,
                                                null, "__dynamic_proxy__");
                  reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
                  return pc;

@@ -484,10 +520,27 @@
                   */
                  throw new IllegalArgumentException(e.toString());
              }
          }
  
+         /**
+          * Called from VM native code to define a proxy class to be stored in archivedData.
+          */
+         private static Class<?> defineProxyClassForCDS(ClassLoader loader, String proxyName, Class<?>[] interfaces,
+                                                        int accessFlags) {
+             ArrayList<Class<?>> list = new ArrayList<>();
+             for (Object o : interfaces) {
+                 list.add((Class<?>)o);
+             }
+ 
+             ProxyBuilder builder = new ProxyBuilder(loader, list);
+             Constructor<?> cons = builder.build();
+             Class<?> proxyClass = cons.getDeclaringClass();
+             archivedData.putArchivedProxyClass(loader, proxyName, list, proxyClass);
+             return proxyClass;
+         }
+ 
          /**
           * Test if given class is a class defined by
           * {@link #defineProxyClass(ProxyClassContext, List)}
           */
          static boolean isProxyClass(Class<?> c) {

@@ -747,11 +800,11 @@
                  // opens the package of the non-public proxy class for java.base to access
                  if (targetModule.isNamed()) {
                      Modules.addOpens(targetModule, targetPackageName, Proxy.class.getModule());
                  }
                  // return the module of the package-private interface
-                 return new ProxyClassContext(targetModule, targetPackageName, 0);
+                 return new ProxyClassContext(targetModule, targetPackageName, 0, false);
              }
  
              // All proxy interfaces are public.  So maps to a dynamic proxy module
              // and add reads edge and qualified exports, if necessary
              Module targetModule = getDynamicModule(loader);

@@ -764,11 +817,11 @@
                  ensureAccess(targetModule, c);
              }
  
              var pkgName = nonExported ? PROXY_PACKAGE_PREFIX + '.' + targetModule.getName()
                                        : targetModule.getName();
-             return new ProxyClassContext(targetModule, pkgName, Modifier.PUBLIC);
+             return new ProxyClassContext(targetModule, pkgName, Modifier.PUBLIC, true);
          }
  
          /*
           * Ensure the given module can access the given class.
           */

@@ -821,26 +874,174 @@
           * Each class loader will have one dynamic module.
           */
          private static Module getDynamicModule(ClassLoader loader) {
              return dynProxyModules.computeIfAbsent(loader, (ld, clv) -> {
                  // create a dynamic module and setup module access
-                 String mn = "jdk.proxy" + counter.incrementAndGet();
+                 int num = counter.incrementAndGet();
+                 String mn = "jdk.proxy" + num;
                  String pn = PROXY_PACKAGE_PREFIX + "." + mn;
                  ModuleDescriptor descriptor =
                          ModuleDescriptor.newModule(mn, Set.of(SYNTHETIC))
                                          .packages(Set.of(pn, mn))
                                          .exports(mn)
                                          .build();
                  Module m = Modules.defineModule(ld, descriptor, null);
-                 Modules.addReads(m, Proxy.class.getModule());
-                 Modules.addExports(m, mn);
-                 // java.base to create proxy instance and access its Lookup instance
-                 Modules.addOpens(m, pn, Proxy.class.getModule());
-                 Modules.addOpens(m, mn, Proxy.class.getModule());
+                 openDynamicModule(m);
+ 
+                 if (CDS.isDumpingHeap() && archivedData != null) {
+                     archivedData.recordModule(loader, m, num);
+                 }
                  return m;
              });
          }
+ 
+         private static void openDynamicModule(Module m) {
+             String mn = m.getName();
+             String pn = PROXY_PACKAGE_PREFIX + "." + mn;
+             Modules.addReads(m, Proxy.class.getModule());
+             Modules.addExports(m, mn);
+             // java.base to create proxy instance and access its Lookup instance
+             Modules.addOpens(m, pn, Proxy.class.getModule());
+             Modules.addOpens(m, mn, Proxy.class.getModule());
+         }
+ 
+         static class ArchivedData {
+             static class InterfacesKey {
+                 Class<?>[] intfsArray;
+                 InterfacesKey(List<Class<?>> intfs) {
+                     intfsArray = new Class<?>[intfs.size()];
+                     for (int i = 0; i < intfs.size(); i++) {
+                         intfsArray[i] = intfs.get(i);
+                     }
+                 }
+                 @Override
+                 public int hashCode() {
+                     return Arrays.hashCode(intfsArray);
+                 }
+                 @Override
+                 public boolean equals(Object other) {
+                     if (other instanceof InterfacesKey) {
+                         InterfacesKey o = (InterfacesKey)other;
+                         int len = intfsArray.length;
+                         if (len != o.intfsArray.length) {
+                             return false;
+                         }
+                         Class<?>[] oa = o.intfsArray;
+                         for (int i = 0; i < len; i++) {
+                             if (intfsArray[i] != oa[i]) {
+                                 return false;
+                             }
+                         }
+                         return true;
+                     } else {
+                         return false;
+                     }
+                 }
+             }
+ 
+             ClassLoader platformLoader;
+             ClassLoader appLoader;
+             HashMap<InterfacesKey,Class<?>> bootCache = new HashMap<>();
+             HashMap<InterfacesKey,Class<?>> platformCache = new HashMap<>();
+             HashMap<InterfacesKey,Class<?>> appCache = new HashMap<>();
+ 
+             Module bootModule;
+             Module platformModule;
+             Module appModule;
+             int maxNum;
+ 
+             ArchivedData(ClassLoader plat, ClassLoader app) {
+                 platformLoader = plat;
+                 appLoader = app;
+             }
+ 
+             HashMap<InterfacesKey,Class<?>> cacheForLoader(ClassLoader loader) {
+                 if (loader == null) {
+                     return bootCache;
+                 } else if (loader == platformLoader) {
+                     return platformCache;
+                 } else if (loader == appLoader) {
+                     return appCache;
+                 } else {
+                     return null;
+                 }
+             }
+ 
+             void recordModule(ClassLoader loader, Module m, int num) {
+                 if (loader == null) {
+                     bootModule = m;
+                 } else if (loader == platformLoader) {
+                     platformModule = m;
+                 } else if (loader == appLoader) {
+                     appModule = m;
+                 } else {
+                     throw new UnsupportedOperationException("Class loader " + loader + " is not supported");
+                 }
+                 if (maxNum < num) {
+                     maxNum = num;
+                 }
+             }
+ 
+             void restore() {
+                 // The info for addReads/addExports/addOpens are maintained solely inside the VM.
+                 // CDS currently doesn't properly archive such info for the dynamically generated modules,
+                 // so we have to recreate them at runtime.
+                 //
+                 // TODO --  consider improving CDS to archive the above info, so we can avoid calling openDynamicModule()
+                 if (bootModule != null) {
+                     Module last = dynProxyModules.putIfAbsent(null, bootModule);
+                     assert last == null;
+                     openDynamicModule(bootModule);
+                 }
+                 if (platformModule != null) {
+                     Module last = dynProxyModules.putIfAbsent(platformLoader, platformModule);
+                     assert last == null;
+                     openDynamicModule(platformModule);
+                 }
+                 if (appModule != null) {
+                     Module last = dynProxyModules.putIfAbsent(appLoader, appModule);
+                     assert last == null;
+                     openDynamicModule(appModule);
+                 }
+ 
+                 while (maxNum > counter.get()) {
+                     counter.incrementAndGet();
+                 }
+             }
+ 
+ 
+             Class<?> getArchivedProxyClass(ClassLoader loader, String proxyPrefix, List<Class<?>> interfaces) {
+                 HashMap<InterfacesKey,Class<?>> cache = cacheForLoader(loader);
+                 if (cache != null && cache.size() > 0) {
+                     InterfacesKey key = new InterfacesKey(interfaces);
+                     return cache.get(key);
+                 } else {
+                     return null;
+                 }
+             }
+ 
+             void putArchivedProxyClass(ClassLoader loader, String proxyName, List<Class<?>> interfaces, Class<?> cls) {
+                 HashMap<InterfacesKey,Class<?>> cache = cacheForLoader(loader);
+                 if (cache != null) {
+                     InterfacesKey key = new InterfacesKey(interfaces);
+                     cache.put(key, cls);
+                 }
+             }
+         }
+ 
+         static ArchivedData archivedData;
+ 
+         static {
+             CDS.initializeFromArchive(ProxyBuilder.class);
+             if (archivedData != null) {
+                 archivedData.restore();
+             }
+         }
+ 
+         private static void initCacheForCDS(ClassLoader platformLoader, ClassLoader appLoader) {
+             archivedData = new ArchivedData(platformLoader, appLoader);
+         }
      }
  
      /**
       * Returns a proxy instance for the specified interfaces
       * that dispatches method invocations to the specified invocation
< prev index next >