changeset 14113:15098f2f8736

8152115: (proxy) Examine performance of dynamic proxy creation Summary: redesign caching of dynamic Proxy classes Reviewed-by: mchung
author plevart
date Mon, 11 Apr 2016 10:55:03 +0200
parents 6478cfbf6f29
children 76a2d0b17dc2
files src/java.base/share/classes/java/lang/ClassLoader.java src/java.base/share/classes/java/lang/System.java src/java.base/share/classes/java/lang/reflect/AbstractClassLoaderValue.java src/java.base/share/classes/java/lang/reflect/ClassLoaderValue.java src/java.base/share/classes/java/lang/reflect/Proxy.java src/java.base/share/classes/java/lang/reflect/WeakCache.java src/java.base/share/classes/jdk/internal/loader/BootLoader.java src/java.base/share/classes/jdk/internal/misc/JavaLangAccess.java test/java/lang/reflect/ClassLoaderValue/Driver.java test/java/lang/reflect/ClassLoaderValue/java.base/java/lang/reflect/ClassLoaderValueTest.java
diffstat 10 files changed, 994 insertions(+), 611 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/lang/ClassLoader.java	Mon Apr 11 16:46:52 2016 +0900
+++ b/src/java.base/share/classes/java/lang/ClassLoader.java	Mon Apr 11 10:55:03 2016 +0200
@@ -2625,6 +2625,25 @@
     // the ServiceCatalog for modules associated with this class loader.
     private volatile ServicesCatalog servicesCatalog;
 
+    /**
+     * Returns the ConcurrentHashMap used as a storage for ClassLoaderValue(s)
+     * associated with this ClassLoader, creating it if it doesn't already exist.
+     */
+    ConcurrentHashMap<?, ?> createOrGetClassLoaderValueMap() {
+        ConcurrentHashMap<?, ?> map = classLoaderValueMap;
+        if (map == null) {
+            map = new ConcurrentHashMap<>();
+            boolean set = trySetObjectField("classLoaderValueMap", map);
+            if (!set) {
+                // beaten by someone else
+                map = classLoaderValueMap;
+            }
+        }
+        return map;
+    }
+
+    // the storage for ClassLoaderValue(s) associated with this ClassLoader
+    private volatile ConcurrentHashMap<?, ?> classLoaderValueMap;
 
     /**
      * Attempts to atomically set a volatile field in this object. Returns
--- a/src/java.base/share/classes/java/lang/System.java	Mon Apr 11 16:46:52 2016 +0900
+++ b/src/java.base/share/classes/java/lang/System.java	Mon Apr 11 10:55:03 2016 +0200
@@ -49,6 +49,7 @@
 import java.security.PrivilegedAction;
 import java.nio.channels.Channel;
 import java.nio.channels.spi.SelectorProvider;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Stream;
 
 import java.util.Objects;
@@ -2026,6 +2027,9 @@
             public ServicesCatalog createOrGetServicesCatalog(ClassLoader cl) {
                 return cl.createOrGetServicesCatalog();
             }
+            public ConcurrentHashMap<?, ?> createOrGetClassLoaderValueMap(ClassLoader cl) {
+                return cl.createOrGetClassLoaderValueMap();
+            }
             public Class<?> findBootstrapClassOrNull(ClassLoader cl, String name) {
                 return cl.findBootstrapClassOrNull(name);
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/reflect/AbstractClassLoaderValue.java	Mon Apr 11 10:55:03 2016 +0200
@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect;
+
+import jdk.internal.loader.BootLoader;
+import jdk.internal.misc.JavaLangAccess;
+import jdk.internal.misc.SharedSecrets;
+
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+
+/**
+ * AbstractClassLoaderValue is a superclass of root-{@link ClassLoaderValue}
+ * and {@link Sub sub}-ClassLoaderValue.
+ *
+ * @param <CLV> the type of concrete ClassLoaderValue (this type)
+ * @param <V>   the type of values associated with ClassLoaderValue
+ */
+abstract class AbstractClassLoaderValue<CLV extends AbstractClassLoaderValue<CLV, V>, V> {
+
+    /**
+     * Sole constructor.
+     */
+    AbstractClassLoaderValue() {}
+
+    /**
+     * Returns the key component of this ClassLoaderValue. The key component of
+     * the root-{@link ClassLoaderValue} is the ClassLoaderValue itself,
+     * while the key component of a {@link #sub(Object) sub}-ClassLoaderValue
+     * is what was given to construct it.
+     *
+     * @return the key component of this ClassLoaderValue.
+     */
+    public abstract Object key();
+
+    /**
+     * Constructs new sub-ClassLoaderValue of this ClassLoaderValue with given
+     * key component.
+     *
+     * @param key the key component of the sub-ClassLoaderValue.
+     * @param <K> the type of the key component.
+     * @return a sub-ClassLoaderValue of this ClassLoaderValue for given key
+     */
+    public <K> Sub<K> sub(K key) {
+        return new Sub<>(key);
+    }
+
+    /**
+     * Returns {@code true} if this ClassLoaderValue is equal to given {@code clv}
+     * or if this ClassLoaderValue was derived from given {@code clv} by a chain
+     * of {@link #sub(Object)} invocations.
+     *
+     * @param clv the ClassLoaderValue to test this against
+     * @return if this ClassLoaderValue is equal to given {@code clv} or
+     * its descendant
+     */
+    public abstract boolean isEqualOrDescendantOf(AbstractClassLoaderValue<?, V> clv);
+
+    /**
+     * Returns the value associated with this ClassLoaderValue and given ClassLoader
+     * or {@code null} if there is none.
+     *
+     * @param cl the ClassLoader for the associated value
+     * @return the value associated with this ClassLoaderValue and given ClassLoader
+     * or {@code null} if there is none.
+     */
+    public V get(ClassLoader cl) {
+        Object val = AbstractClassLoaderValue.<CLV>map(cl).get(this);
+        try {
+            return extractValue(val);
+        } catch (Memoizer.RecursiveInvocationException e) {
+            // propagate recursive get() for the same key that is just
+            // being calculated in computeIfAbsent()
+            throw e;
+        } catch (Throwable t) {
+            // don't propagate exceptions thrown from Memoizer - pretend
+            // that there was no entry
+            // (computeIfAbsent invocation will try to remove it anyway)
+            return null;
+        }
+    }
+
+    /**
+     * Associates given value {@code v} with this ClassLoaderValue and given
+     * ClassLoader and returns {@code null} if there was no previously associated
+     * value or does nothing and returns previously associated value if there
+     * was one.
+     *
+     * @param cl the ClassLoader for the associated value
+     * @param v  the value to associate
+     * @return previously associated value or null if there was none
+     */
+    public V putIfAbsent(ClassLoader cl, V v) {
+        ConcurrentHashMap<CLV, Object> map = map(cl);
+        @SuppressWarnings("unchecked")
+        CLV clv = (CLV) this;
+        while (true) {
+            try {
+                Object val = map.putIfAbsent(clv, v);
+                return extractValue(val);
+            } catch (Memoizer.RecursiveInvocationException e) {
+                // propagate RecursiveInvocationException for the same key that
+                // is just being calculated in computeIfAbsent
+                throw e;
+            } catch (Throwable t) {
+                // don't propagate exceptions thrown from foreign Memoizer -
+                // pretend that there was no entry and retry
+                // (foreign computeIfAbsent invocation will try to remove it anyway)
+            }
+            // TODO:
+            // Thread.onSpinLoop(); // when available
+        }
+    }
+
+    /**
+     * Removes the value associated with this ClassLoaderValue and given
+     * ClassLoader if the associated value is equal to given value {@code v} and
+     * returns {@code true} or does nothing and returns {@code false} if there is
+     * no currently associated value or it is not equal to given value {@code v}.
+     *
+     * @param cl the ClassLoader for the associated value
+     * @param v  the value to compare with currently associated value
+     * @return {@code true} if the association was removed or {@code false} if not
+     */
+    public boolean remove(ClassLoader cl, Object v) {
+        return AbstractClassLoaderValue.<CLV>map(cl).remove(this, v);
+    }
+
+    /**
+     * Returns the value associated with this ClassLoaderValue and given
+     * ClassLoader if there is one or computes the value by invoking given
+     * {@code mappingFunction}, associates it and returns it.
+     * <p>
+     * Computation and association of the computed value is performed atomically
+     * by the 1st thread that requests a particular association while holding a
+     * lock associated with this ClassLoaderValue and given ClassLoader.
+     * Nested calls from the {@code mappingFunction} to {@link #get},
+     * {@link #putIfAbsent} or {@link #computeIfAbsent} for the same association
+     * are not allowed and throw {@link IllegalStateException}. Nested call to
+     * {@link #remove} for the same association is allowed but will always return
+     * {@code false} regardless of passed-in comparison value. Nested calls for
+     * other association(s) are allowed, but care should be taken to avoid
+     * deadlocks. When two threads perform nested computations of the overlapping
+     * set of associations they should always request them in the same order.
+     *
+     * @param cl              the ClassLoader for the associated value
+     * @param mappingFunction the function to compute the value
+     * @return the value associated with this ClassLoaderValue and given
+     * ClassLoader.
+     * @throws IllegalStateException if a direct or indirect invocation from
+     *                               within given {@code mappingFunction} that
+     *                               computes the value of a particular association
+     *                               to {@link #get}, {@link #putIfAbsent} or
+     *                               {@link #computeIfAbsent}
+     *                               for the same association is attempted.
+     */
+    public V computeIfAbsent(ClassLoader cl,
+                             BiFunction<
+                                 ? super ClassLoader,
+                                 ? super CLV,
+                                 ? extends V
+                                 > mappingFunction) throws IllegalStateException {
+        ConcurrentHashMap<CLV, Object> map = map(cl);
+        @SuppressWarnings("unchecked")
+        CLV clv = (CLV) this;
+        Memoizer<CLV, V> mv = null;
+        while (true) {
+            Object val = (mv == null) ? map.get(clv) : map.putIfAbsent(clv, mv);
+            if (val == null) {
+                if (mv == null) {
+                    // create Memoizer lazily when 1st needed and restart loop
+                    mv = new Memoizer<>(cl, clv, mappingFunction);
+                    continue;
+                }
+                // mv != null, therefore sv == null was a result of successful
+                // putIfAbsent
+                try {
+                    // trigger Memoizer to compute the value
+                    V v = mv.get();
+                    // attempt to replace our Memoizer with the value
+                    map.replace(clv, mv, v);
+                    // return computed value
+                    return v;
+                } catch (Throwable t) {
+                    // our Memoizer has thrown, attempt to remove it
+                    map.remove(clv, mv);
+                    // propagate exception because it's from our Memoizer
+                    throw t;
+                }
+            } else {
+                try {
+                    return extractValue(val);
+                } catch (Memoizer.RecursiveInvocationException e) {
+                    // propagate recursive attempts to calculate the same
+                    // value as being calculated at the moment
+                    throw e;
+                } catch (Throwable t) {
+                    // don't propagate exceptions thrown from foreign Memoizer -
+                    // pretend that there was no entry and retry
+                    // (foreign computeIfAbsent invocation will try to remove it anyway)
+                }
+            }
+            // TODO:
+            // Thread.onSpinLoop(); // when available
+        }
+    }
+
+    /**
+     * Removes all values associated with given ClassLoader {@code cl} and
+     * {@link #isEqualOrDescendantOf(AbstractClassLoaderValue) this or descendants}
+     * of this ClassLoaderValue.
+     * This is not an atomic operation. Other threads may see some associations
+     * be already removed and others still present while this method is executing.
+     * <p>
+     * The sole intention of this method is to cleanup after a unit test that
+     * tests ClassLoaderValue directly. It is not intended for use in
+     * actual algorithms.
+     *
+     * @param cl the associated ClassLoader of the values to be removed
+     */
+    public void removeAll(ClassLoader cl) {
+        ConcurrentHashMap<CLV, Object> map = map(cl);
+        for (Iterator<CLV> i = map.keySet().iterator(); i.hasNext(); ) {
+            if (i.next().isEqualOrDescendantOf(this)) {
+                i.remove();
+            }
+        }
+    }
+
+    private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
+
+    /**
+     * @return a ConcurrentHashMap for given ClassLoader
+     */
+    @SuppressWarnings("unchecked")
+    private static <CLV extends AbstractClassLoaderValue<CLV, ?>>
+    ConcurrentHashMap<CLV, Object> map(ClassLoader cl) {
+        return (ConcurrentHashMap<CLV, Object>)
+            (cl == null ? BootLoader.getClassLoaderValueMap()
+                        : JLA.createOrGetClassLoaderValueMap(cl));
+    }
+
+    /**
+     * @return value extracted from the {@link Memoizer} if given
+     * {@code memoizerOrValue} parameter is a {@code Memoizer} or
+     * just return given parameter.
+     */
+    @SuppressWarnings("unchecked")
+    private V extractValue(Object memoizerOrValue) {
+        if (memoizerOrValue instanceof Memoizer) {
+            return ((Memoizer<?, V>) memoizerOrValue).get();
+        } else {
+            return (V) memoizerOrValue;
+        }
+    }
+
+    /**
+     * A memoized supplier that invokes given {@code mappingFunction} just once
+     * and remembers the result or thrown exception for subsequent calls.
+     * If given mappingFunction returns null, it is converted to NullPointerException,
+     * thrown from the Memoizer's {@link #get()} method and remembered.
+     * If the Memoizer is invoked recursively from the given {@code mappingFunction},
+     * {@link RecursiveInvocationException} is thrown, but it is not remembered.
+     * The in-flight call to the {@link #get()} can still complete successfully if
+     * such exception is handled by the mappingFunction.
+     */
+    private static final class Memoizer<CLV extends AbstractClassLoaderValue<CLV, V>, V>
+        implements Supplier<V> {
+
+        private final ClassLoader cl;
+        private final CLV clv;
+        private final BiFunction<? super ClassLoader, ? super CLV, ? extends V>
+            mappingFunction;
+
+        private volatile V v;
+        private volatile Throwable t;
+        private boolean inCall;
+
+        Memoizer(ClassLoader cl,
+                 CLV clv,
+                 BiFunction<? super ClassLoader, ? super CLV, ? extends V>
+                     mappingFunction
+        ) {
+            this.cl = cl;
+            this.clv = clv;
+            this.mappingFunction = mappingFunction;
+        }
+
+        @Override
+        public V get() throws RecursiveInvocationException {
+            V v = this.v;
+            if (v != null) return v;
+            Throwable t = this.t;
+            if (t == null) {
+                synchronized (this) {
+                    if ((v = this.v) == null && (t = this.t) == null) {
+                        if (inCall) {
+                            throw new RecursiveInvocationException();
+                        }
+                        inCall = true;
+                        try {
+                            this.v = v = Objects.requireNonNull(
+                                mappingFunction.apply(cl, clv));
+                        } catch (Throwable x) {
+                            this.t = t = x;
+                        } finally {
+                            inCall = false;
+                        }
+                    }
+                }
+            }
+            if (v != null) return v;
+            if (t instanceof Error) {
+                throw (Error) t;
+            } else if (t instanceof RuntimeException) {
+                throw (RuntimeException) t;
+            } else {
+                throw new UndeclaredThrowableException(t);
+            }
+        }
+
+        static class RecursiveInvocationException extends IllegalStateException {
+            private static final long serialVersionUID = 1L;
+
+            RecursiveInvocationException() {
+                super("Recursive call");
+            }
+        }
+    }
+
+    /**
+     * sub-ClassLoaderValue is an inner class of {@link AbstractClassLoaderValue}
+     * and also a subclass of it. It can therefore be instantiated as an inner
+     * class of either an instance of root-{@link ClassLoaderValue} or another
+     * instance of itself. This enables composing type-safe compound keys of
+     * arbitrary length:
+     * <pre>{@code
+     * ClassLoaderValue<V> clv = new ClassLoaderValue<>();
+     * ClassLoaderValue<V>.Sub<K1>.Sub<K2>.Sub<K3> clv_k123 =
+     *     clv.sub(k1).sub(k2).sub(k3);
+     * }</pre>
+     * From which individual components are accessible in a type-safe way:
+     * <pre>{@code
+     * K1 k1 = clv_k123.parent().parent().key();
+     * K2 k2 = clv_k123.parent().key();
+     * K3 k3 = clv_k123.key();
+     * }</pre>
+     * This allows specifying non-capturing lambdas for the mapping function of
+     * {@link #computeIfAbsent(ClassLoader, BiFunction)} operation that can
+     * access individual key components from passed-in
+     * sub-[sub-...]ClassLoaderValue instance in a type-safe way.
+     *
+     * @param <K> the type of {@link #key()} component contained in the
+     *            sub-ClassLoaderValue.
+     */
+    final class Sub<K> extends AbstractClassLoaderValue<Sub<K>, V> {
+
+        private final K key;
+
+        Sub(K key) {
+            this.key = key;
+        }
+
+        /**
+         * @return the parent ClassLoaderValue this sub-ClassLoaderValue
+         * has been {@link #sub(Object) derived} from.
+         */
+        public AbstractClassLoaderValue<CLV, V> parent() {
+            return AbstractClassLoaderValue.this;
+        }
+
+        /**
+         * @return the key component of this sub-ClassLoaderValue.
+         */
+        @Override
+        public K key() {
+            return key;
+        }
+
+        /**
+         * sub-ClassLoaderValue is a descendant of given {@code clv} if it is
+         * either equal to it or if its {@link #parent() parent} is a
+         * descendant of given {@code clv}.
+         */
+        @Override
+        public boolean isEqualOrDescendantOf(AbstractClassLoaderValue<?, V> clv) {
+            return equals(Objects.requireNonNull(clv)) ||
+                   parent().isEqualOrDescendantOf(clv);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof Sub)) return false;
+            @SuppressWarnings("unchecked")
+            Sub<?> that = (Sub<?>) o;
+            return this.parent().equals(that.parent()) &&
+                   Objects.equals(this.key, that.key);
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * parent().hashCode() +
+                   Objects.hashCode(key);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/reflect/ClassLoaderValue.java	Mon Apr 11 10:55:03 2016 +0200
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect;
+
+import java.util.Objects;
+import java.util.function.BiFunction;
+
+/**
+ * root-ClassLoaderValue. Each instance defines a separate namespace for
+ * associated values.
+ * <p>
+ * ClassLoaderValue allows associating a
+ * {@link #computeIfAbsent(ClassLoader, BiFunction) computed} non-null value with
+ * a {@code (ClassLoader, keys...)} tuple. The associated value, as well as the
+ * keys are strongly reachable from the associated ClassLoader so care should be
+ * taken to use such keys and values that only reference types resolvable from
+ * the associated ClassLoader. Failing that, ClassLoader leaks are inevitable.
+ * <p>
+ * Example usage:
+ * <pre>{@code
+ * // create a root instance which represents a namespace and declares the type of
+ * // associated values (Class instances in this example)
+ * static final ClassLoaderValue<Class<?>> proxyClasses = new ClassLoaderValue<>();
+ *
+ * // create a compound key composed of a Module and a list of interfaces
+ * Module module = ...;
+ * List<Class<?>> interfaces = ...;
+ * ClassLoaderValue<Class<?>>.Sub<Module>.Sub<List<Class<?>>> key =
+ *     proxyClasses.sub(module).sub(interfaces);
+ *
+ * // use the compound key together with ClassLoader to lazily associate
+ * // the value with tuple (loader, module, interfaces) and return it
+ * ClassLoader loader = ...;
+ * Class<?> proxyClass = key.computeIfAbsent(loader, (ld, ky) -> {
+ *     List<Class<?>> intfcs = ky.key();
+ *     Module m = ky.parent().key();
+ *     Class<?> clazz = defineProxyClass(ld, m, intfcs);
+ *     return clazz;
+ * });
+ * }</pre>
+ * <p>
+ * {@code classLoaderValue.<operation>(classLoader, ...)} represents an operation
+ * to {@link #get}, {@link #putIfAbsent}, {@link #computeIfAbsent} or {@link #remove}
+ * a value associated with a (classLoader, classLoaderValue) tuple. ClassLoader
+ * instances and root-{@link ClassLoaderValue} instances are compared using
+ * identity equality while {@link Sub sub}-ClassLoaderValue instances define
+ * {@link #equals(Object) equality} in terms of equality of its
+ * {@link Sub#parent() parent} ClassLoaderValue and its
+ * {@link #key() key} component.
+ *
+ * @param <V> the type of value(s) associated with the root-ClassLoaderValue and
+ *            all its {@link #sub(Object) descendants}.
+ * @author Peter Levart
+ * @since 9
+ */
+final class ClassLoaderValue<V>
+    extends AbstractClassLoaderValue<ClassLoaderValue<V>, V> {
+
+    /**
+     * Constructs new root-ClassLoaderValue representing its own namespace.
+     */
+    public ClassLoaderValue() {}
+
+    /**
+     * @return the key component of this root-ClassLoaderValue (itself).
+     */
+    @Override
+    public ClassLoaderValue<V> key() {
+        return this;
+    }
+
+    /**
+     * root-ClassLoaderValue can only be equal to itself and has no predecessors.
+     */
+    @Override
+    public boolean isEqualOrDescendantOf(AbstractClassLoaderValue<?, V> clv) {
+        return equals(Objects.requireNonNull(clv));
+    }
+}
--- a/src/java.base/share/classes/java/lang/reflect/Proxy.java	Mon Apr 11 16:46:52 2016 +0900
+++ b/src/java.base/share/classes/java/lang/reflect/Proxy.java	Mon Apr 11 10:55:03 2016 +0200
@@ -25,7 +25,6 @@
 
 package java.lang.reflect;
 
-import java.lang.ref.WeakReference;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.Arrays;
@@ -39,10 +38,8 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.WeakHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.BiFunction;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -284,6 +281,13 @@
         { InvocationHandler.class };
 
     /**
+     * a cache of proxy constructors with
+     * {@link Constructor#setAccessible(boolean) accessible} flag already set
+     */
+    private static final ClassLoaderValue<Constructor<?>> proxyCache =
+        new ClassLoaderValue<>();
+
+    /**
      * the invocation handler for this proxy instance.
      * @serial
      */
@@ -361,14 +365,55 @@
                                          Class<?>... interfaces)
         throws IllegalArgumentException
     {
-        final List<Class<?>> intfs = List.of(interfaces);  // interfaces cloned
-        final SecurityManager sm = System.getSecurityManager();
-        final Class<?> caller = Reflection.getCallerClass();
-        if (sm != null) {
-            checkProxyAccess(caller, loader, intfs);
+        Class<?> caller = System.getSecurityManager() == null
+                              ? null
+                              : Reflection.getCallerClass();
+
+        return getProxyConstructor(caller, loader, interfaces)
+            .getDeclaringClass();
+    }
+
+    /**
+     * Returns the {@code Constructor} object of a proxy class that takes a
+     * single argument of type {@link InvocationHandler}, given a class loader
+     * and an array of interfaces. The returned constructor will have the
+     * {@link Constructor#setAccessible(boolean) accessible} flag already set.
+     *
+     * @param   caller passed from a public-facing @CallerSensitive method if
+     *                 SecurityManager is set or {@code null} if there's no
+     *                 SecurityManager
+     * @param   loader the class loader to define the proxy class
+     * @param   interfaces the list of interfaces for the proxy class
+     *          to implement
+     * @return  a Constructor of the proxy class taking single
+     *          {@code InvocationHandler} parameter
+     */
+    private static Constructor<?> getProxyConstructor(Class<?> caller,
+                                                      ClassLoader loader,
+                                                      Class<?>... interfaces)
+    {
+        // optimization for single interface
+        if (interfaces.length == 1) {
+            Class<?> intf = interfaces[0];
+            if (caller != null) {
+                checkProxyAccess(caller, loader, intf);
+            }
+            return proxyCache.sub(intf).computeIfAbsent(
+                loader,
+                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
+            );
+        } else {
+            // interfaces cloned
+            final Class<?>[] intfsArray = interfaces.clone();
+            if (caller != null) {
+                checkProxyAccess(caller, loader, intfsArray);
+            }
+            final List<Class<?>> intfs = Arrays.asList(intfsArray);
+            return proxyCache.sub(intfs).computeIfAbsent(
+                loader,
+                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
+            );
         }
-
-        return new ProxyBuilder(loader, intfs).build();
     }
 
     /*
@@ -391,7 +436,7 @@
      */
     private static void checkProxyAccess(Class<?> caller,
                                          ClassLoader loader,
-                                         List<Class<?>> interfaces)
+                                         Class<?> ... interfaces)
     {
         SecurityManager sm = System.getSecurityManager();
         if (sm != null) {
@@ -399,147 +444,18 @@
             if (VM.isSystemDomainLoader(loader) && !VM.isSystemDomainLoader(ccl)) {
                 sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
             }
-            ReflectUtil.checkProxyPackageAccess(ccl, interfaces.toArray(EMPTY_CLASS_ARRAY));
-        }
-    }
-
-    /*
-     * a key used for proxy class with 0 implemented interfaces
-     */
-    private static final Object key0 = new Object();
-
-    /*
-     * Key1 and Key2 are optimized for the common use of dynamic proxies
-     * that implement 1 or 2 interfaces.
-     */
-
-    /*
-     * a key used for proxy class with 1 implemented interface
-     */
-    private static final class Key1 extends WeakReference<Class<?>> {
-        private final int hash;
-
-        Key1(Class<?> intf) {
-            super(intf);
-            this.hash = intf.hashCode();
-        }
-
-        @Override
-        public int hashCode() {
-            return hash;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            Class<?> intf;
-            return this == obj ||
-                   obj != null &&
-                   obj.getClass() == Key1.class &&
-                   (intf = get()) != null &&
-                   intf == ((Key1) obj).get();
-        }
-    }
-
-    /*
-     * a key used for proxy class with 2 implemented interfaces
-     */
-    private static final class Key2 extends WeakReference<Class<?>> {
-        private final int hash;
-        private final WeakReference<Class<?>> ref2;
-
-        Key2(Class<?> intf1, Class<?> intf2) {
-            super(intf1);
-            hash = 31 * intf1.hashCode() + intf2.hashCode();
-            ref2 = new WeakReference<>(intf2);
-        }
-
-        @Override
-        public int hashCode() {
-            return hash;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            Class<?> intf1, intf2;
-            return this == obj ||
-                   obj != null &&
-                   obj.getClass() == Key2.class &&
-                   (intf1 = get()) != null &&
-                   intf1 == ((Key2) obj).get() &&
-                   (intf2 = ref2.get()) != null &&
-                   intf2 == ((Key2) obj).ref2.get();
-        }
-    }
-
-    /*
-     * a key used for proxy class with any number of implemented interfaces
-     * (used here for 3 or more only)
-     */
-    private static final class KeyX {
-        private final int hash;
-        private final WeakReference<Class<?>>[] refs;
-
-        @SuppressWarnings("unchecked")
-        KeyX(List<Class<?>> interfaces) {
-            hash = Arrays.hashCode(interfaces.toArray());
-            refs = (WeakReference<Class<?>>[])new WeakReference<?>[interfaces.size()];
-            int i = 0;
-            for (Class<?> intf : interfaces) {
-                refs[i++] = new WeakReference<>(intf);
-            }
-        }
-
-        @Override
-        public int hashCode() {
-            return hash;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            return this == obj ||
-                   obj != null &&
-                   obj.getClass() == KeyX.class &&
-                   equals(refs, ((KeyX) obj).refs);
-        }
-
-        private static boolean equals(WeakReference<Class<?>>[] refs1,
-                                      WeakReference<Class<?>>[] refs2) {
-            if (refs1.length != refs2.length) {
-                return false;
-            }
-            for (int i = 0; i < refs1.length; i++) {
-                Class<?> intf = refs1[i].get();
-                if (intf == null || intf != refs2[i].get()) {
-                    return false;
-                }
-            }
-            return true;
+            ReflectUtil.checkProxyPackageAccess(ccl, interfaces);
         }
     }
 
     /**
-     * A function that maps an array of interfaces to an optimal key where
-     * Class objects representing interfaces are weakly referenced.
+     * 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 KeyFactory<T>
-        implements BiFunction<T, List<Class<?>>, Object>
-    {
-        @Override
-        public Object apply(T t, List<Class<?>> interfaces) {
-            switch (interfaces.size()) {
-                case 1: return new Key1(interfaces.get(0)); // the most frequent
-                case 2: return new Key2(interfaces.get(0), interfaces.get(1));
-                case 0: return key0;
-                default: return new KeyX(interfaces);
-            }
-        }
-    }
-
-    /**
-     * A factory function that generates, defines and returns the proxy class
-     * given the ClassLoader and array of interfaces.
-     */
-    private static final class ProxyClassFactory {
+    private static final class ProxyBuilder {
         private static final Unsafe UNSAFE = Unsafe.getUnsafe();
 
         // prefix for all proxy class names
@@ -548,6 +464,10 @@
         // 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 static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
             String proxyPkg = null;     // package to define proxy class in
             int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
@@ -601,8 +521,11 @@
             byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                     proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
             try {
-                return UNSAFE.defineClass(proxyName, proxyClassFile, 0, proxyClassFile.length,
-                                          loader, null);
+                Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
+                                                 0, proxyClassFile.length,
+                                                 loader, null);
+                reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
+                return pc;
             } catch (ClassFormatError e) {
                 /*
                  * A ClassFormatError here means that (barring bugs in the
@@ -616,35 +539,14 @@
         }
 
         /**
-         * Test if the given class is a proxy class
+         * Test if given class is a class defined by
+         * {@link #defineProxyClass(Module, List)}
          */
         static boolean isProxyClass(Class<?> c) {
-            return proxyCache.containsValue(c);
+            return Objects.equals(reverseProxyCache.sub(c).get(c.getClassLoader()),
+                                  Boolean.TRUE);
         }
 
-        /**
-         * Returns the proxy class.  It will return the cached proxy class
-         * if exists; otherwise, it will create the proxy class and store in
-         * the cache.
-         */
-        static Class<?> get(Module module, List<Class<?>> interfaces) {
-            return proxyCache.get(module, interfaces);
-        }
-
-        /**
-         * a cache of proxy classes in the named and unnamed module
-         */
-        private static final WeakCache<Module, List<Class<?>>, Class<?>> proxyCache =
-            new WeakCache<>(new KeyFactory<Module>(),
-                new BiFunction<Module, List<Class<?>>, Class<?>>()  {
-                    @Override
-                    public Class<?> apply(Module m, List<Class<?>> interfaces) {
-                        Objects.requireNonNull(m);
-                        return defineProxyClass(m, interfaces);
-                    }
-            });
-
-
         private static boolean isExportedType(Class<?> c) {
             String pn = c.getPackageName();
             return Modifier.isPublic(c.getModifiers()) && c.getModule().isExported(pn);
@@ -685,25 +587,18 @@
                 }
             });
 
-        private static final boolean isDebug() {
+        private static boolean isDebug() {
             return !DEBUG.isEmpty();
         }
-        private static final boolean isDebug(String flag) {
+        private static boolean isDebug(String flag) {
             return DEBUG.equals(flag);
         }
-    }
 
-    /**
-     * 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 {
-        final ClassLoader loader;
-        final List<Class<?>> interfaces;
-        final Module module;
+        // ProxyBuilder instance members start here....
+
+        private final ClassLoader loader;
+        private final List<Class<?>> interfaces;
+        private final Module module;
         ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) {
             if (!VM.isModuleSystemInited()) {
                 throw new InternalError("Proxy is not supported until module system is fully initialzed");
@@ -723,16 +618,34 @@
             assert getLoader(module) == loader;
         }
 
+        ProxyBuilder(ClassLoader loader, Class<?> intf) {
+            this(loader, Collections.singletonList(intf));
+        }
+
         /**
-         * Generate a proxy class.  If the target module does not have any
+         * Generate a proxy class and return its proxy Constructor with
+         * accessible flag already set. If the target module does not have access
          * to any interface types, IllegalAccessError will be thrown by the VM
          * at defineClass time.
          *
          * Must call the checkProxyAccess method to perform permission checks
          * before calling this.
          */
-        Class<?> build() {
-            return ProxyClassFactory.get(module, interfaces);
+        Constructor<?> build() {
+            Class<?> proxyClass = defineProxyClass(module, interfaces);
+            final Constructor<?> cons;
+            try {
+                cons = proxyClass.getConstructor(constructorParams);
+            } catch (NoSuchMethodException e) {
+                throw new InternalError(e.toString(), e);
+            }
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                public Void run() {
+                    cons.setAccessible(true);
+                    return null;
+                }
+            });
+            return cons;
         }
 
         /**
@@ -742,9 +655,9 @@
          * @throws IllegalArgumentException if it violates the restrictions specified
          *         in {@link Proxy#newProxyInstance}
          */
-        static void validateProxyInterfaces(ClassLoader loader,
-                                            List<Class<?>> interfaces,
-                                            Set<Class<?>> refTypes)
+        private static void validateProxyInterfaces(ClassLoader loader,
+                                                    List<Class<?>> interfaces,
+                                                    Set<Class<?>> refTypes)
         {
             Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.size());
             for (Class<?> intf : interfaces) {
@@ -779,10 +692,11 @@
          * Returns all types referenced by all public method signatures of
          * the proxy interfaces
          */
-        static Set<Class<?>> referencedTypes(ClassLoader loader, List<Class<?>> interfaces) {
+        private static Set<Class<?>> referencedTypes(ClassLoader loader,
+                                                     List<Class<?>> interfaces) {
             return interfaces.stream()
                  .flatMap(intf -> Stream.of(intf.getMethods())
-                                        .flatMap(m -> methodRefTypes(m))
+                                        .flatMap(ProxyBuilder::methodRefTypes)
                                         .map(ProxyBuilder::getElementType)
                                         .filter(t -> !t.isPrimitive()))
                  .collect(Collectors.toSet());
@@ -792,11 +706,11 @@
          * Extracts all types referenced on a method signature including
          * its return type, parameter types, and exception types.
          */
-        static Stream<Class<?>> methodRefTypes(Method m) {
+        private static Stream<Class<?>> methodRefTypes(Method m) {
             return Stream.of(new Class<?>[] { m.getReturnType() },
                              m.getParameterTypes(),
                              m.getExceptionTypes())
-                         .flatMap(a -> Stream.of(a));
+                         .flatMap(Stream::of);
         }
 
         /**
@@ -813,7 +727,9 @@
          * package.  Reads edge and qualified exports are added for
          * dynamic module to access.
          */
-        static Module mapToModule(ClassLoader loader, List<Class<?>> interfaces, Set<Class<?>> refTypes) {
+        private static Module mapToModule(ClassLoader loader,
+                                          List<Class<?>> interfaces,
+                                          Set<Class<?>> refTypes) {
             Map<Class<?>, Module> modulePrivateTypes = new HashMap<>();
             Map<Class<?>, Module> packagePrivateTypes = new HashMap<>();
             for (Class<?> intf : interfaces) {
@@ -884,10 +800,9 @@
             Set<Class<?>> visited = new HashSet<>();
             while (!deque.isEmpty()) {
                 Class<?> c = deque.poll();
-                if (visited.contains(c)) {
+                if (!visited.add(c)) {
                     continue;
                 }
-                visited.add(c);
                 ensureAccess(target, c);
 
                 // add all superinterfaces
@@ -906,7 +821,7 @@
         /*
          * Ensure the given module can access the given class.
          */
-        static void ensureAccess(Module target, Class<?> c) {
+        private static void ensureAccess(Module target, Class<?> c) {
             Module m = c.getModule();
             // add read edge and qualified export for the target module to access
             if (!target.canRead(m)) {
@@ -921,7 +836,7 @@
         /*
          * Ensure the given class is visible to the class loader.
          */
-        static void ensureVisible(ClassLoader ld, Class<?> c) {
+        private static void ensureVisible(ClassLoader ld, Class<?> c) {
             Class<?> type = null;
             try {
                 type = Class.forName(c.getName(), false, ld);
@@ -933,7 +848,7 @@
             }
         }
 
-        static Class<?> getElementType(Class<?> type) {
+        private static Class<?> getElementType(Class<?> type) {
             Class<?> e = type;
             while (e.isArray()) {
                 e = e.getComponentType();
@@ -941,7 +856,8 @@
             return e;
         }
 
-        private static final WeakHashMap<ClassLoader, Module> dynProxyModules = new WeakHashMap<>();
+        private static final ClassLoaderValue<Module> dynProxyModules =
+            new ClassLoaderValue<>();
         private static final AtomicInteger counter = new AtomicInteger();
 
         /*
@@ -950,12 +866,12 @@
          *
          * Each class loader will have one dynamic module.
          */
-        static Module getDynamicModule(ClassLoader loader) {
-            return dynProxyModules.computeIfAbsent(loader, ld -> {
+        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();
                 String pn = PROXY_PACKAGE_PREFIX + "." + mn;
-                Module m = Modules.defineModule(loader, mn, Collections.singleton(pn));
+                Module m = Modules.defineModule(ld, mn, Collections.singleton(pn));
                 Modules.addReads(m, Proxy.class.getModule());
                 // java.base to create proxy instance
                 Modules.addExports(m, pn, Object.class.getModule());
@@ -1062,40 +978,31 @@
                                           InvocationHandler h) {
         Objects.requireNonNull(h);
 
-        final List<Class<?>> intfs = List.of(interfaces);  // interfaces cloned
-        final SecurityManager sm = System.getSecurityManager();
-        final Class<?> caller = Reflection.getCallerClass();
-        if (sm != null) {
-            checkProxyAccess(caller, loader, intfs);
-        }
+        final Class<?> caller = System.getSecurityManager() == null
+                                    ? null
+                                    : Reflection.getCallerClass();
 
         /*
-         * Look up or generate the designated proxy class.
+         * Look up or generate the designated proxy class and its constructor.
          */
-        Class<?> cl = new ProxyBuilder(loader, intfs).build();
+        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
 
-        return newProxyInstance(cl, caller, h);
+        return newProxyInstance(caller, cons, h);
     }
 
-    private static Object newProxyInstance(Class<?> proxyClass, Class<?> caller, InvocationHandler h) {
+    private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
+                                           Constructor<?> cons,
+                                           InvocationHandler h) {
         /*
          * Invoke its constructor with the designated invocation handler.
          */
         try {
-            final SecurityManager sm = System.getSecurityManager();
-            if (sm != null) {
-                checkNewProxyPermission(caller, proxyClass);
+            if (caller != null) {
+                checkNewProxyPermission(caller, cons.getDeclaringClass());
             }
 
-            final Constructor<?> cons = proxyClass.getConstructor(constructorParams);
-            AccessController.doPrivileged(new PrivilegedAction<Void>() {
-                public Void run() {
-                    cons.setAccessible(true);
-                    return null;
-                }
-            });
             return cons.newInstance(new Object[]{h});
-        } catch (IllegalAccessException | InstantiationException | NoSuchMethodException e) {
+        } catch (IllegalAccessException | InstantiationException e) {
             throw new InternalError(e.toString(), e);
         } catch (InvocationTargetException e) {
             Throwable t = e.getCause();
@@ -1150,7 +1057,7 @@
      * @throws  NullPointerException if {@code cl} is {@code null}
      */
     public static boolean isProxyClass(Class<?> cl) {
-        return Proxy.class.isAssignableFrom(cl) && ProxyClassFactory.isProxyClass(cl);
+        return Proxy.class.isAssignableFrom(cl) && ProxyBuilder.isProxyClass(cl);
     }
 
     /**
--- a/src/java.base/share/classes/java/lang/reflect/WeakCache.java	Mon Apr 11 16:46:52 2016 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,381 +0,0 @@
-/*
- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package java.lang.reflect;
-
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.function.BiFunction;
-import java.util.function.Supplier;
-
-/**
- * Cache mapping pairs of {@code (key, sub-key) -> value}. Keys and values are
- * weakly but sub-keys are strongly referenced.  Keys are passed directly to
- * {@link #get} method which also takes a {@code parameter}. Sub-keys are
- * calculated from keys and parameters using the {@code subKeyFactory} function
- * passed to the constructor. Values are calculated from keys and parameters
- * using the {@code valueFactory} function passed to the constructor.
- * Keys can be {@code null} and are compared by identity while sub-keys returned by
- * {@code subKeyFactory} or values returned by {@code valueFactory}
- * can not be null. Sub-keys are compared using their {@link #equals} method.
- * Entries are expunged from cache lazily on each invocation to {@link #get},
- * {@link #containsValue} or {@link #size} methods when the WeakReferences to
- * keys are cleared. Cleared WeakReferences to individual values don't cause
- * expunging, but such entries are logically treated as non-existent and
- * trigger re-evaluation of {@code valueFactory} on request for their
- * key/subKey.
- *
- * @author Peter Levart
- * @param <K> type of keys
- * @param <P> type of parameters
- * @param <V> type of values
- */
-final class WeakCache<K, P, V> {
-
-    private final ReferenceQueue<K> refQueue
-        = new ReferenceQueue<>();
-    // the key type is Object for supporting null key
-    private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
-        = new ConcurrentHashMap<>();
-    private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
-        = new ConcurrentHashMap<>();
-    private final BiFunction<K, P, ?> subKeyFactory;
-    private final BiFunction<K, P, V> valueFactory;
-
-    /**
-     * Construct an instance of {@code WeakCache}
-     *
-     * @param subKeyFactory a function mapping a pair of
-     *                      {@code (key, parameter) -> sub-key}
-     * @param valueFactory  a function mapping a pair of
-     *                      {@code (key, parameter) -> value}
-     * @throws NullPointerException if {@code subKeyFactory} or
-     *                              {@code valueFactory} is null.
-     */
-    public WeakCache(BiFunction<K, P, ?> subKeyFactory,
-                     BiFunction<K, P, V> valueFactory) {
-        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
-        this.valueFactory = Objects.requireNonNull(valueFactory);
-    }
-
-    /**
-     * Look-up the value through the cache. This always evaluates the
-     * {@code subKeyFactory} function and optionally evaluates
-     * {@code valueFactory} function if there is no entry in the cache for given
-     * pair of (key, subKey) or the entry has already been cleared.
-     *
-     * @param key       possibly null key
-     * @param parameter parameter used together with key to create sub-key and
-     *                  value (should not be null)
-     * @return the cached value (never null)
-     * @throws NullPointerException if {@code parameter} passed in or
-     *                              {@code sub-key} calculated by
-     *                              {@code subKeyFactory} or {@code value}
-     *                              calculated by {@code valueFactory} is null.
-     */
-    public V get(K key, P parameter) {
-        Objects.requireNonNull(parameter);
-
-        expungeStaleEntries();
-
-        Object cacheKey = CacheKey.valueOf(key, refQueue);
-
-        // lazily install the 2nd level valuesMap for the particular cacheKey
-        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
-        if (valuesMap == null) {
-            ConcurrentMap<Object, Supplier<V>> oldValuesMap
-                = map.putIfAbsent(cacheKey,
-                                  valuesMap = new ConcurrentHashMap<>());
-            if (oldValuesMap != null) {
-                valuesMap = oldValuesMap;
-            }
-        }
-
-        // create subKey and retrieve the possible Supplier<V> stored by that
-        // subKey from valuesMap
-        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
-        Supplier<V> supplier = valuesMap.get(subKey);
-        Factory factory = null;
-
-        while (true) {
-            if (supplier != null) {
-                // supplier might be a Factory or a CacheValue<V> instance
-                V value = supplier.get();
-                if (value != null) {
-                    return value;
-                }
-            }
-            // else no supplier in cache
-            // or a supplier that returned null (could be a cleared CacheValue
-            // or a Factory that wasn't successful in installing the CacheValue)
-
-            // lazily construct a Factory
-            if (factory == null) {
-                factory = new Factory(key, parameter, subKey, valuesMap);
-            }
-
-            if (supplier == null) {
-                supplier = valuesMap.putIfAbsent(subKey, factory);
-                if (supplier == null) {
-                    // successfully installed Factory
-                    supplier = factory;
-                }
-                // else retry with winning supplier
-            } else {
-                if (valuesMap.replace(subKey, supplier, factory)) {
-                    // successfully replaced
-                    // cleared CacheEntry / unsuccessful Factory
-                    // with our Factory
-                    supplier = factory;
-                } else {
-                    // retry with current supplier
-                    supplier = valuesMap.get(subKey);
-                }
-            }
-        }
-    }
-
-    /**
-     * Checks whether the specified non-null value is already present in this
-     * {@code WeakCache}. The check is made using identity comparison regardless
-     * of whether value's class overrides {@link Object#equals} or not.
-     *
-     * @param value the non-null value to check
-     * @return true if given {@code value} is already cached
-     * @throws NullPointerException if value is null
-     */
-    public boolean containsValue(V value) {
-        Objects.requireNonNull(value);
-
-        expungeStaleEntries();
-        return reverseMap.containsKey(new LookupValue<>(value));
-    }
-
-    /**
-     * Returns the current number of cached entries that
-     * can decrease over time when keys/values are GC-ed.
-     */
-    public int size() {
-        expungeStaleEntries();
-        return reverseMap.size();
-    }
-
-    @SuppressWarnings("unchecked") // refQueue.poll actually returns CacheKey<K>
-    private void expungeStaleEntries() {
-        CacheKey<K> cacheKey;
-        while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {
-            cacheKey.expungeFrom(map, reverseMap);
-        }
-    }
-
-    /**
-     * A factory {@link Supplier} that implements the lazy synchronized
-     * construction of the value and installment of it into the cache.
-     */
-    private final class Factory implements Supplier<V> {
-
-        private final K key;
-        private final P parameter;
-        private final Object subKey;
-        private final ConcurrentMap<Object, Supplier<V>> valuesMap;
-
-        Factory(K key, P parameter, Object subKey,
-                ConcurrentMap<Object, Supplier<V>> valuesMap) {
-            this.key = key;
-            this.parameter = parameter;
-            this.subKey = subKey;
-            this.valuesMap = valuesMap;
-        }
-
-        @Override
-        public synchronized V get() { // serialize access
-            // re-check
-            Supplier<V> supplier = valuesMap.get(subKey);
-            if (supplier != this) {
-                // something changed while we were waiting:
-                // might be that we were replaced by a CacheValue
-                // or were removed because of failure ->
-                // return null to signal WeakCache.get() to retry
-                // the loop
-                return null;
-            }
-            // else still us (supplier == this)
-
-            // create new value
-            V value = null;
-            try {
-                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
-            } finally {
-                if (value == null) { // remove us on failure
-                    valuesMap.remove(subKey, this);
-                }
-            }
-            // the only path to reach here is with non-null value
-            assert value != null;
-
-            // wrap value with CacheValue (WeakReference)
-            CacheValue<V> cacheValue = new CacheValue<>(value);
-
-            // try replacing us with CacheValue (this should always succeed)
-            if (valuesMap.replace(subKey, this, cacheValue)) {
-                // put also in reverseMap
-                reverseMap.put(cacheValue, Boolean.TRUE);
-            } else {
-                throw new AssertionError("Should not reach here");
-            }
-
-            // successfully replaced us with new CacheValue -> return the value
-            // wrapped by it
-            return value;
-        }
-    }
-
-    /**
-     * Common type of value suppliers that are holding a referent.
-     * The {@link #equals} and {@link #hashCode} of implementations is defined
-     * to compare the referent by identity.
-     */
-    private interface Value<V> extends Supplier<V> {}
-
-    /**
-     * An optimized {@link Value} used to look-up the value in
-     * {@link WeakCache#containsValue} method so that we are not
-     * constructing the whole {@link CacheValue} just to look-up the referent.
-     */
-    private static final class LookupValue<V> implements Value<V> {
-        private final V value;
-
-        LookupValue(V value) {
-            this.value = value;
-        }
-
-        @Override
-        public V get() {
-            return value;
-        }
-
-        @Override
-        public int hashCode() {
-            return System.identityHashCode(value); // compare by identity
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            return obj == this ||
-                   obj instanceof Value &&
-                   this.value == ((Value<?>) obj).get();  // compare by identity
-        }
-    }
-
-    /**
-     * A {@link Value} that weakly references the referent.
-     */
-    private static final class CacheValue<V>
-        extends WeakReference<V> implements Value<V>
-    {
-        private final int hash;
-
-        CacheValue(V value) {
-            super(value);
-            this.hash = System.identityHashCode(value); // compare by identity
-        }
-
-        @Override
-        public int hashCode() {
-            return hash;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            V value;
-            return obj == this ||
-                   obj instanceof Value &&
-                   // cleared CacheValue is only equal to itself
-                   (value = get()) != null &&
-                   value == ((Value<?>) obj).get(); // compare by identity
-        }
-    }
-
-    /**
-     * CacheKey containing a weakly referenced {@code key}. It registers
-     * itself with the {@code refQueue} so that it can be used to expunge
-     * the entry when the {@link WeakReference} is cleared.
-     */
-    private static final class CacheKey<K> extends WeakReference<K> {
-
-        // a replacement for null keys
-        private static final Object NULL_KEY = new Object();
-
-        static <K> Object valueOf(K key, ReferenceQueue<K> refQueue) {
-            return key == null
-                   // null key means we can't weakly reference it,
-                   // so we use a NULL_KEY singleton as cache key
-                   ? NULL_KEY
-                   // non-null key requires wrapping with a WeakReference
-                   : new CacheKey<>(key, refQueue);
-        }
-
-        private final int hash;
-
-        private CacheKey(K key, ReferenceQueue<K> refQueue) {
-            super(key, refQueue);
-            this.hash = System.identityHashCode(key);  // compare by identity
-        }
-
-        @Override
-        public int hashCode() {
-            return hash;
-        }
-
-        @Override
-        @SuppressWarnings("unchecked")
-        public boolean equals(Object obj) {
-            K key;
-            return obj == this ||
-                   obj != null &&
-                   obj.getClass() == this.getClass() &&
-                   // cleared CacheKey is only equal to itself
-                   (key = this.get()) != null &&
-                   // compare key by identity
-                   key == ((CacheKey<K>) obj).get(); // Cast is safe from getClass check
-        }
-
-        void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
-                         ConcurrentMap<?, Boolean> reverseMap) {
-            // removing just by key is always safe here because after a CacheKey
-            // is cleared and enqueue-ed it is only equal to itself
-            // (see equals method)...
-            ConcurrentMap<?, ?> valuesMap = map.remove(this);
-            // remove also from reverseMap if needed
-            if (valuesMap != null) {
-                for (Object cacheValue : valuesMap.values()) {
-                    reverseMap.remove(cacheValue);
-                }
-            }
-        }
-    }
-}
--- a/src/java.base/share/classes/jdk/internal/loader/BootLoader.java	Mon Apr 11 16:46:52 2016 +0900
+++ b/src/java.base/share/classes/jdk/internal/loader/BootLoader.java	Mon Apr 11 10:55:03 2016 +0200
@@ -39,6 +39,7 @@
 import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.jar.JarInputStream;
 import java.util.jar.Manifest;
 import java.util.stream.Stream;
@@ -68,6 +69,10 @@
     // ServiceCatalog for the boot class loader
     private static final ServicesCatalog SERVICES_CATALOG = new ServicesCatalog();
 
+    // ClassLoaderValue map for boot class loader
+    private static final ConcurrentHashMap<?, ?> CLASS_LOADER_VALUE_MAP =
+        new ConcurrentHashMap<>();
+
     /**
      * Returns the unnamed module for the boot loader.
      */
@@ -83,6 +88,13 @@
     }
 
     /**
+     * Returns the ClassLoaderValue map for the boot class loader.
+     */
+    public static ConcurrentHashMap<?, ?> getClassLoaderValueMap() {
+        return CLASS_LOADER_VALUE_MAP;
+    }
+
+    /**
      * Register a module with this class loader so that its classes (and
      * resources) become visible via this class loader.
      */
--- a/src/java.base/share/classes/jdk/internal/misc/JavaLangAccess.java	Mon Apr 11 16:46:52 2016 +0900
+++ b/src/java.base/share/classes/jdk/internal/misc/JavaLangAccess.java	Mon Apr 11 10:55:03 2016 +0200
@@ -33,6 +33,7 @@
 import java.net.URL;
 import java.security.AccessControlContext;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Stream;
 
 import jdk.internal.module.ServicesCatalog;
@@ -146,6 +147,12 @@
     ServicesCatalog createOrGetServicesCatalog(ClassLoader cl);
 
     /**
+     * Returns the ConcurrentHashMap used as a storage for ClassLoaderValue(s)
+     * associated with the given class loader, creating it if it doesn't already exist.
+     */
+    ConcurrentHashMap<?, ?> createOrGetClassLoaderValueMap(ClassLoader cl);
+
+    /**
      * Returns a class loaded by the bootstrap class loader.
      */
     Class<?> findBootstrapClassOrNull(ClassLoader cl, String name);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/reflect/ClassLoaderValue/Driver.java	Mon Apr 11 10:55:03 2016 +0200
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8152115
+ * @summary functional and concurrency test for ClassLoaderValue
+ * @build java.base/java.lang.reflect.ClassLoaderValueTest
+ * @run main Driver
+ */
+public class Driver {
+    public static void main(String[] args) throws Exception {
+        java.lang.reflect.ClassLoaderValueTest.main(args);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/reflect/ClassLoaderValue/java.base/java/lang/reflect/ClassLoaderValueTest.java	Mon Apr 11 10:55:03 2016 +0200
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Functional and concurrency test for ClassLoaderValue
+ *
+ * @author Peter Levart
+ */
+public class ClassLoaderValueTest {
+
+    @SuppressWarnings("unchecked")
+    public static void main(String[] args) throws Exception {
+
+        ClassLoaderValue[] clvs = {new ClassLoaderValue<>(),
+                                   new ClassLoaderValue<>()};
+
+        ClassLoader[] lds = {ClassLoader.getSystemClassLoader(),
+                             ClassLoader.getPlatformClassLoader(),
+                             null /* bootstrap class loader */};
+
+        Integer[] keys = new Integer[32];
+        for (int i = 0; i < keys.length; i++) {
+            keys[i] = i + 128;
+        }
+
+        try (AutoCloseable cleanup = () -> {
+            for (ClassLoaderValue<Integer> clv : clvs) {
+                for (ClassLoader ld : lds) {
+                    clv.removeAll(ld);
+                }
+            }
+        }) {
+            // 1st just one sequential pass of single-threaded validation
+            // which is easier to debug if it fails...
+            for (ClassLoaderValue<Integer> clv : clvs) {
+                for (ClassLoader ld : lds) {
+                    writeValidateOps(clv, ld, keys);
+                }
+            }
+            for (ClassLoaderValue<Integer> clv : clvs) {
+                for (ClassLoader ld : lds) {
+                    readValidateOps(clv, ld, keys);
+                }
+            }
+
+            // 2nd the same in concurrent setting that also validates
+            // failure-isolation between threads and data-isolation between
+            // regions - (ClassLoader, ClassLoaderValue) pairs - of the storage
+            testConcurrentIsolation(clvs, lds, keys, TimeUnit.SECONDS.toMillis(3));
+        }
+    }
+
+    static void writeValidateOps(ClassLoaderValue<Integer> clv,
+                                 ClassLoader ld,
+                                 Object[] keys) {
+        for (int i = 0; i < keys.length; i++) {
+            Object k = keys[i];
+            Integer v1 = i;
+            Integer v2 = i + 333;
+            Integer pv;
+            boolean success;
+
+            pv = clv.sub(k).putIfAbsent(ld, v1);
+            assertEquals(pv, null);
+            assertEquals(clv.sub(k).get(ld), v1);
+
+            pv = clv.sub(k).putIfAbsent(ld, v2);
+            assertEquals(pv, v1);
+            assertEquals(clv.sub(k).get(ld), v1);
+
+            success = clv.sub(k).remove(ld, v2);
+            assertEquals(success, false);
+            assertEquals(clv.sub(k).get(ld), v1);
+
+            success = clv.sub(k).remove(ld, v1);
+            assertEquals(success, true);
+            assertEquals(clv.sub(k).get(ld), null);
+
+            pv = clv.sub(k).putIfAbsent(ld, v2);
+            assertEquals(pv, null);
+            assertEquals(clv.sub(k).get(ld), v2);
+
+            pv = clv.sub(k).computeIfAbsent(ld, (_ld, _clv) -> v1);
+            assertEquals(pv, v2);
+            assertEquals(clv.sub(k).get(ld), v2);
+
+            success = clv.sub(k).remove(ld, v1);
+            assertEquals(success, false);
+            assertEquals(clv.sub(k).get(ld), v2);
+
+            success = clv.sub(k).remove(ld, v2);
+            assertEquals(success, true);
+            assertEquals(clv.sub(k).get(ld), null);
+
+            pv = clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> {
+                try {
+                    // nested get for same key should throw
+                    clv_k.get(_ld);
+                    throw new AssertionError("Unexpected code path");
+                } catch (IllegalStateException e) {
+                    // expected
+                }
+                try {
+                    // nested putIfAbsent for same key should throw
+                    clv_k.putIfAbsent(_ld, v1);
+                    throw new AssertionError("Unexpected code path");
+                } catch (IllegalStateException e) {
+                    // expected
+                }
+                // nested remove for for same key and any value (even null)
+                // should return false
+                assertEquals(clv_k.remove(_ld, null), false);
+                assertEquals(clv_k.remove(_ld, v1), false);
+                assertEquals(clv_k.remove(_ld, v2), false);
+                try {
+                    // nested computeIfAbsent for same key should throw
+                    clv_k.computeIfAbsent(_ld, (__ld, _clv_k) -> v1);
+                    throw new AssertionError("Unexpected code path");
+                } catch (IllegalStateException e) {
+                    // expected
+                }
+                // if everything above has been handled, we should succeed...
+                return v2;
+            });
+            // ... and the result should be reflected in the CLV
+            assertEquals(pv, v2);
+            assertEquals(clv.sub(k).get(ld), v2);
+
+            success = clv.sub(k).remove(ld, v2);
+            assertEquals(success, true);
+            assertEquals(clv.sub(k).get(ld), null);
+
+            try {
+                clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> {
+                    throw new UnsupportedOperationException();
+                });
+                throw new AssertionError("Unexpected code path");
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+            assertEquals(clv.sub(k).get(ld), null);
+        }
+    }
+
+    static void readValidateOps(ClassLoaderValue<Integer> clv,
+                                ClassLoader ld,
+                                Object[] keys) {
+        for (int i = 0; i < keys.length; i++) {
+            Object k = keys[i];
+            Integer v1 = i;
+            Integer v2 = i + 333;
+            Integer rv = clv.sub(k).get(ld);
+            if (!(rv == null || rv.equals(v1) || rv.equals(v2))) {
+                throw new AssertionError("Unexpected value: " + rv +
+                                         ", expected one of: null, " + v1 + ", " + v2);
+            }
+        }
+    }
+
+    static void testConcurrentIsolation(ClassLoaderValue<Integer>[] clvs,
+                                        ClassLoader[] lds,
+                                        Object[] keys,
+                                        long millisRuntime) {
+        ExecutorService exe = Executors.newCachedThreadPool();
+        List<Future<?>> futures = new ArrayList<>();
+        AtomicBoolean stop = new AtomicBoolean();
+        for (ClassLoaderValue<Integer> clv : clvs) {
+            for (ClassLoader ld : lds) {
+                // submit a task that exercises a mix of modifying
+                // and reading-validating operations in an isolated
+                // part of the storage. If isolation is violated,
+                // validation operations are expected to fail.
+                futures.add(exe.submit(() -> {
+                    do {
+                        writeValidateOps(clv, ld, keys);
+                    } while (!stop.get());
+                }));
+                // submit a task that just reads from the same part of
+                // the storage as above task. It should not disturb
+                // above task in any way and this task should never
+                // exhibit any failure although above task produces
+                // regular failures during lazy computation
+                futures.add(exe.submit(() -> {
+                    do {
+                        readValidateOps(clv, ld, keys);
+                    } while (!stop.get());
+                }));
+            }
+        }
+        // wait for some time
+        try {
+            Thread.sleep(millisRuntime);
+        } catch (InterruptedException e) {
+            throw new AssertionError(e);
+        }
+        // stop tasks
+        stop.set(true);
+        // collect results
+        AssertionError error = null;
+        for (Future<?> future : futures) {
+            try {
+                future.get();
+            } catch (InterruptedException | ExecutionException e) {
+                if (error == null) error = new AssertionError("Failure");
+                error.addSuppressed(e);
+            }
+        }
+        exe.shutdown();
+        if (error != null) throw error;
+    }
+
+    static void assertEquals(Object actual, Object expected) {
+        if (!Objects.equals(actual, expected)) {
+            throw new AssertionError("Expected: " + expected + ", actual: " + actual);
+        }
+    }
+}