8164547: Make java.lang.reflect.ClassLoaderValue public for internal use
authorplevart
Mon, 22 Aug 2016 13:16:51 +0200
changeset 40459 88a91142b711
parent 40457 21ae06f40614
child 40460 14c7efbe6e30
8164547: Make java.lang.reflect.ClassLoaderValue public for internal use Summary: Move it to jdk.internal.loader and make it public Reviewed-by: shade, alanb
jdk/src/java.base/share/classes/java/lang/reflect/AbstractClassLoaderValue.java
jdk/src/java.base/share/classes/java/lang/reflect/ClassLoaderValue.java
jdk/src/java.base/share/classes/java/lang/reflect/Proxy.java
jdk/src/java.base/share/classes/jdk/internal/loader/AbstractClassLoaderValue.java
jdk/src/java.base/share/classes/jdk/internal/loader/ClassLoaderValue.java
jdk/test/java/lang/reflect/ClassLoaderValue/Driver.java
jdk/test/java/lang/reflect/ClassLoaderValue/java.base/java/lang/reflect/ClassLoaderValueTest.java
jdk/test/jdk/internal/loader/ClassLoaderValue/ClassLoaderValueTest.java
--- a/jdk/src/java.base/share/classes/java/lang/reflect/AbstractClassLoaderValue.java	Mon Aug 22 15:17:11 2016 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,431 +0,0 @@
-/*
- * 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);
-        }
-    }
-}
--- a/jdk/src/java.base/share/classes/java/lang/reflect/ClassLoaderValue.java	Mon Aug 22 15:17:11 2016 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-/*
- * 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/jdk/src/java.base/share/classes/java/lang/reflect/Proxy.java	Mon Aug 22 15:17:11 2016 +0900
+++ b/jdk/src/java.base/share/classes/java/lang/reflect/Proxy.java	Mon Aug 22 13:16:51 2016 +0200
@@ -29,11 +29,9 @@
 import java.security.PrivilegedAction;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -49,6 +47,7 @@
 import jdk.internal.misc.VM;
 import jdk.internal.reflect.CallerSensitive;
 import jdk.internal.reflect.Reflection;
+import jdk.internal.loader.ClassLoaderValue;
 import sun.reflect.misc.ReflectUtil;
 import sun.security.action.GetPropertyAction;
 import sun.security.util.SecurityConstants;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/loader/AbstractClassLoaderValue.java	Mon Aug 22 13:16:51 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 jdk.internal.loader;
+
+import jdk.internal.misc.JavaLangAccess;
+import jdk.internal.misc.SharedSecrets;
+
+import java.lang.reflect.UndeclaredThrowableException;
+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
+ */
+public 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.
+     */
+    public 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/jdk/src/java.base/share/classes/jdk/internal/loader/ClassLoaderValue.java	Mon Aug 22 13:16:51 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 jdk.internal.loader;
+
+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
+ */
+public 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/jdk/test/java/lang/reflect/ClassLoaderValue/Driver.java	Mon Aug 22 15:17:11 2016 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-/*
- * 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);
-    }
-}
--- a/jdk/test/java/lang/reflect/ClassLoaderValue/java.base/java/lang/reflect/ClassLoaderValueTest.java	Mon Aug 22 15:17:11 2016 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,249 +0,0 @@
-/*
- * 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);
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/jdk/internal/loader/ClassLoaderValue/ClassLoaderValueTest.java	Mon Aug 22 13:16:51 2016 +0200
@@ -0,0 +1,251 @@
+/*
+ * 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.
+ */
+
+import jdk.internal.loader.ClassLoaderValue;
+
+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;
+
+/**
+ * @test
+ * @bug 8152115
+ * @summary functional and concurrency test for ClassLoaderValue
+ * @modules java.base/jdk.internal.loader
+ * @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);
+        }
+    }
+}