8152115: (proxy) Examine performance of dynamic proxy creation

Redesign caching of dynamic Proxy classes

Reviewed-by: mchung
This commit is contained in:
Peter Levart 2016-04-11 10:55:03 +02:00
parent aaebc5882b
commit 589c46da46
10 changed files with 994 additions and 611 deletions

View File

@ -2625,6 +2625,25 @@ public abstract class ClassLoader {
// 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

View File

@ -49,6 +49,7 @@ import java.security.AccessController;
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 final class System {
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);
}

View File

@ -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);
}
}
}

View File

@ -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));
}
}

View File

@ -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.List;
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;
@ -283,6 +280,13 @@ public class Proxy implements java.io.Serializable {
private static final Class<?>[] constructorParams =
{ 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 @@ public class Proxy implements java.io.Serializable {
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 new ProxyBuilder(loader, intfs).build();
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()
);
}
}
/*
@ -391,7 +436,7 @@ public class Proxy implements java.io.Serializable {
*/
private static void checkProxyAccess(Class<?> caller,
ClassLoader loader,
List<Class<?>> interfaces)
Class<?> ... interfaces)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
@ -399,147 +444,18 @@ public class Proxy implements java.io.Serializable {
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 @@ public class Proxy implements java.io.Serializable {
// 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 @@ public class Proxy implements java.io.Serializable {
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 @@ public class Proxy implements java.io.Serializable {
}
/**
* Test if the given class is a proxy class
* Test if given class is a class defined by
* {@link #defineProxyClass(Module, List)}
*/
static boolean isProxyClass(Class<?> c) {
return proxyCache.containsValue(c);
return Objects.equals(reverseProxyCache.sub(c).get(c.getClassLoader()),
Boolean.TRUE);
}
/**
* Returns the proxy class. It will return the cached proxy class
* if exists; otherwise, it will create the proxy class and store in
* the cache.
*/
static Class<?> get(Module module, List<Class<?>> interfaces) {
return proxyCache.get(module, interfaces);
}
/**
* a cache of proxy classes in the named and unnamed module
*/
private static final WeakCache<Module, List<Class<?>>, Class<?>> proxyCache =
new WeakCache<>(new KeyFactory<Module>(),
new BiFunction<Module, List<Class<?>>, Class<?>>() {
@Override
public Class<?> apply(Module m, List<Class<?>> interfaces) {
Objects.requireNonNull(m);
return defineProxyClass(m, interfaces);
}
});
private static boolean isExportedType(Class<?> c) {
String pn = c.getPackageName();
return Modifier.isPublic(c.getModifiers()) && c.getModule().isExported(pn);
@ -685,25 +587,18 @@ public class Proxy implements java.io.Serializable {
}
});
private static final boolean isDebug() {
private static boolean isDebug() {
return !DEBUG.isEmpty();
}
private static final boolean isDebug(String flag) {
private static boolean isDebug(String flag) {
return DEBUG.equals(flag);
}
}
/**
* Builder for a proxy class.
*
* If the module is not specified in this ProxyBuilder constructor,
* it will map from the given loader and interfaces to the module
* in which the proxy class will be defined.
*/
private static final class ProxyBuilder {
final ClassLoader loader;
final List<Class<?>> interfaces;
final Module module;
// ProxyBuilder instance members start here....
private final ClassLoader loader;
private final List<Class<?>> interfaces;
private final Module module;
ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) {
if (!VM.isModuleSystemInited()) {
throw new InternalError("Proxy is not supported until module system is fully initialzed");
@ -723,16 +618,34 @@ public class Proxy implements java.io.Serializable {
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 @@ public class Proxy implements java.io.Serializable {
* @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 @@ public class Proxy implements java.io.Serializable {
* 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 @@ public class Proxy implements java.io.Serializable {
* 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 @@ public class Proxy implements java.io.Serializable {
* 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 @@ public class Proxy implements java.io.Serializable {
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 @@ public class Proxy implements java.io.Serializable {
/*
* 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 @@ public class Proxy implements java.io.Serializable {
/*
* 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 @@ public class Proxy implements java.io.Serializable {
}
}
static Class<?> getElementType(Class<?> type) {
private static Class<?> getElementType(Class<?> type) {
Class<?> e = type;
while (e.isArray()) {
e = e.getComponentType();
@ -941,7 +856,8 @@ public class Proxy implements java.io.Serializable {
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 @@ public class Proxy implements java.io.Serializable {
*
* 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 @@ public class Proxy implements java.io.Serializable {
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 @@ public class Proxy implements java.io.Serializable {
* @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);
}
/**

View File

@ -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);
}
}
}
}
}

View File

@ -39,6 +39,7 @@ import java.security.PrivilegedAction;
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 @@ public class BootLoader {
// 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.
*/
@ -82,6 +87,13 @@ public class BootLoader {
return SERVICES_CATALOG;
}
/**
* 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.

View File

@ -33,6 +33,7 @@ import java.lang.reflect.Module;
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;
@ -145,6 +146,12 @@ public interface JavaLangAccess {
*/
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.
*/

View File

@ -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);
}
}

View File

@ -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);
}
}
}