8152115: (proxy) Examine performance of dynamic proxy creation
Summary: redesign caching of dynamic Proxy classes
Reviewed-by: mchung
--- a/jdk/src/java.base/share/classes/java/lang/ClassLoader.java Mon Apr 11 16:46:52 2016 +0900
+++ b/jdk/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/jdk/src/java.base/share/classes/java/lang/System.java Mon Apr 11 16:46:52 2016 +0900
+++ b/jdk/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/jdk/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/jdk/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/jdk/src/java.base/share/classes/java/lang/reflect/Proxy.java Mon Apr 11 16:46:52 2016 +0900
+++ b/jdk/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);
- }
-
- /**
- * 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);
+ return Objects.equals(reverseProxyCache.sub(c).get(c.getClassLoader()),
+ Boolean.TRUE);
}
- /**
- * 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);
}
- }
+
+ // ProxyBuilder instance members start here....
- /**
- * 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;
+ 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/jdk/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/jdk/src/java.base/share/classes/jdk/internal/loader/BootLoader.java Mon Apr 11 16:46:52 2016 +0900
+++ b/jdk/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/jdk/src/java.base/share/classes/jdk/internal/misc/JavaLangAccess.java Mon Apr 11 16:46:52 2016 +0900
+++ b/jdk/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/jdk/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/jdk/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);
+ }
+ }
+}